package com.basker.pisces.domain.property.meta;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.meta.annotation.CollectionField;
import com.basker.pisces.core.meta.annotation.DataField;
import com.basker.pisces.domain.ISupportEntityBuild;
import com.basker.pisces.domain.constant.DomainConstants.ObjectErrorCode;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.enums.EnumTypeManager;
import com.basker.pisces.domain.property.InvalidPropertyValueException;
import com.basker.pisces.domain.value.EnumOptionValue;
import com.basker.pisces.domain.value.meta.EnumOption;
import com.basker.pisces.domain.value.meta.EnumType;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.resource.StringResources;
import com.basker.pisces.service.exception.IObjectErrorsWrapper.ObjectError;

public class EnumProperty extends Property<EnumOptionValue> implements ISupportEntityBuild {

    @CollectionField
    private List<EnumOption> options = new ArrayList<>();

    @DataField
    private String enumTypeName;

    // 为了让id有序
    private transient Map<String, EnumOption> optionMap = new TreeMap<>();

    public EnumProperty() {

    }

    public void addOption(Object id, String value) {
        this.options.add(new EnumOption(String.valueOf(id), value));
    }

    @Override
    public void build(Entity entity) {
        Assert.notEmpty(options, "EnumProperty '" + this.toString() + "' options is empty!");

        this.optionMap.clear();

        for (EnumOption option : options) {
            String id = option.getId();

            Assert.hasText(id, "option's id is required!");

            EnumOption existOption = this.optionMap.get(id);
            if (existOption != null) {
                throw ExceptionFactory.createRuntimeException("pisces-domain.EnumProperty.build-001",
                        "EnumOption:{0} is exist!", id);
            }

            this.optionMap.put(id, option);
        }
    }

    @Override
    public EnumOptionValue createValue(Object rawValue) {
        EnumOption option = null;

        if (rawValue != null) {
            option = this.getOption(rawValue.toString());
        }

        if (option == null) {
            return EnumOptionValue.EMPTY;
        } else {
            return new EnumOptionValue(option);
        }
    }

    /**
     * 获取对应的枚举类型
     *
     * @return
     * @see EnumTypeManager
     */
    public String getEnumTypeName() {
        return enumTypeName;
    }

    public EnumOption getOption(Object id) {
        return this.optionMap.get(String.valueOf(id));
    }

    public List<EnumOption> getOptions() {
        return options;
    }

    /**
     * 通过{@link #getEnumTypeName()}加载选项
     */
    public final void loadEnumType() {
        if (StringUtils.isEmpty(this.getEnumTypeName())) {
            return;
        }

        this.options.clear();

        EnumType enumType = EnumTypeManager.getInstance().getEnumType(this.getEnumTypeName());
        if (enumType == null) {
            return;
        }

        try {
            StringResources.suspandResovle();

            for (EnumOption enumOption : enumType.getOptions()) {
                this.addOption(enumOption.getId(), enumOption.getDisplayName());
            }
        } finally {
            StringResources.resumeResovle();
        }
    }

    public void setEnumTypeName(String enumTypeName) {
        this.enumTypeName = enumTypeName;
    }

    public void setOptions(List<EnumOption> options) {
        this.options = options;
    }

    @Override
    public void setValue(Object dataObject, Object inputValue) {
        EnumOption option = null;

        if (inputValue != null) {
            option = this.getOption(inputValue.toString());

            if (option == null) {
                ObjectError objectError = new ObjectError(ObjectErrorCode.ENUM_INVALID, dataObject, "",
                        this.getInvalidInputValueMessage(this.toString(), inputValue));
                throw new InvalidPropertyValueException(Arrays.asList(objectError));
            }
        }

        this.setRawValue(dataObject, inputValue);
    }

    @Override
    protected String getDefaultInvalidInputValueMessage(String propertyDesc, Object inputValue) {
        return StringResources.get("pisces-domain.EnumProperty.InvalidOptionValue", propertyDesc, inputValue);
    }

}
